﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using Ooganizer.Platform.Win32;
using Ooganizer.Server;

namespace Ooganizer.Platform
{
    public class ScreenCapture
    {
        /// <summary>
        /// Saves a Window using a DC of the Full Screen, then crops the window out of the
        /// full screen capture. We have to do this because SaveWindowUsingDCNBitBlt() doesn't
        /// work for all programs that overwrite the glass area on Aero (like MS Office)
        /// </summary>
        /// <remarks>
        /// IMP! - This function will work on some multiple monitor configurations but currently NOT all!
        /// If for example, the primary display is on the left and sec on the right, and the window is on the
        /// right monitor, then all x and y values will be positive, (We currently only handle multiple monitors,
        /// where one x or y value is negative!),
        /// ~more calculations must be done for multiple x, multiple y positive configurations
        /// </remarks>
        /// <param name="hWnd">handle to a Window</param>
        public Image SaveWindowUsingFullScreenShotCrop(IntPtr hWnd)
        {
            // First get the Window Rect
            RECT rectWnd;
            Win32Functions.GetWindowRect(hWnd, out rectWnd);

            // Now figure out which monitor the window is in
            IntPtr hMonitor = Win32Functions.MonitorFromRect(ref rectWnd, (int) Win32_Constants.MONITOR_DEFAULTTONEAREST);

            // Get the szDevice Name for the Monitor
            MONITORINFOEX monitorinfoex = new MONITORINFOEX();
            monitorinfoex.cbSize = Marshal.SizeOf(monitorinfoex);
            if (!Win32Functions.GetMonitorInfo(hMonitor, ref monitorinfoex))
                return null;

            // Use szDevice to create a DC
            IntPtr hDC = Win32Functions.CreateDC(monitorinfoex.szDeviceName, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

            //Here we make a compatible device context in memory for screen device context.
            IntPtr hMemDC = Win32Functions.CreateCompatibleDC(hDC);

            // Let's get all the monitor dimensions we need
            Rectangle rectDim = monitorinfoex.rcMonitor.AsRectangle;
            int DisplayHeight = rectDim.Height;
            int DisplayWidth = rectDim.Width;

            //The .net way to retrieve dimensions            
            Screen screen = Screen.FromHandle(hWnd);            

            //We create a compatible bitmap of screen size using screen device context.
            IntPtr hBitmap = Win32Functions.CreateCompatibleBitmap(hDC, DisplayWidth, DisplayHeight);

            if (hBitmap != IntPtr.Zero)
            {
                //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
                IntPtr hOld = (IntPtr)Win32Functions.SelectObject(hMemDC, hBitmap);

                //We copy the Bitmap to the memory device context.
                Win32Functions.BitBlt(hMemDC, 0, 0, DisplayWidth, DisplayHeight, hDC, 0, 0, Win32_Constants.SRCCOPY);

                //We select the old bitmap back to the memory device context.
                Win32Functions.SelectObject(hMemDC, hOld);

                //We delete the memory device context.
                Win32Functions.DeleteDC(hMemDC);

                //Delete the screen device context.
                Win32Functions.DeleteDC(hDC);

                //Image is created by Image bitmap handle and stored in local variable.
                Image image = (Image)System.Drawing.Image.FromHbitmap(hBitmap);

                // * For Debugging *
                //string strFileNameNPathWExtPNG = (Env.GetSnapshotDirectory() + @"\" + hWnd.ToString() + ".png");
                //image.Save(strFileNameNPathWExtPNG, System.Drawing.Imaging.ImageFormat.Png);

                //Release the memory for compatible bitmap.
                Win32Functions.DeleteObject(hBitmap);

                ////
                // Imp! - Now we want to crop the Window out of the Screen Image
                // NOTE! - Currently NOT all Multiple Monitor configurations are supported!.
                // ++ x and ++ Y configurations will be inacturate when cropping!, more work TODO
                ////
                Rectangle rectangleWnd = rectWnd.AsRectangle;
                if (rectangleWnd.X < 0) // calc the x pos if (-)
                {
                    rectangleWnd.X = rectangleWnd.X + DisplayWidth;
                }
                if (rectangleWnd.Y < 0) // calc the y pos if (-)
                {
                    rectangleWnd.Y = rectangleWnd.Y + DisplayHeight;
                }
                //Bitmap bmpImage = new Bitmap(image);                
                //Bitmap bmpCrop = bmpImage.Clone(rectangleWnd, bmpImage.PixelFormat);
                Bitmap bmpCrop = (Bitmap)image;
                bmpCrop = bmpCrop.Clone(rectangleWnd, bmpCrop.PixelFormat);
                return (Image) bmpCrop;
            }

            return null;
        }

       /// <summary>
       /// Saves the Window using a DC, this works well for bUseClientDC = true, but doesn't
       /// work when using the Window's DC because of Aero drawing via directx, the window frame
       /// won't be in the image
       /// </summary>
       /// <param name="hWnd">handle to a Window</param>
       /// <param name="bUseClientDC">true to use ClientDC, false to use WindowDC</param>
       public Image SaveWindowUsingDCNBitBlt(IntPtr hWnd, bool bUseClientDC)
       {           
           SIZE size;
           IntPtr hDC;
           IntPtr hBitmap;
           Image image;
           RECT Rect = new RECT();

           if (bUseClientDC)
            Win32Functions.GetClientRect(hWnd, out Rect);
           else
            Win32Functions.GetWindowRect(hWnd, out Rect);
           
           size.cx = (Rect.right - Rect.left);
           size.cy = (Rect.bottom - Rect.top);

           // TRY USING GetDCEx() - May actually do better
           //hDC = Win32Functions.GetDCEx(hWnd);
           if (bUseClientDC)           
               hDC = Win32Functions.GetDC(hWnd);                          
           else           
               hDC = Win32Functions.GetWindowDC(hWnd);           

           //Here we make a compatible device context in memory for screen device context.
           IntPtr hMemDC = Win32Functions.CreateCompatibleDC(hDC);

           //We create a compatible bitmap of screen size using screen device context.
           hBitmap = Win32Functions.CreateCompatibleBitmap(hDC, size.cx, size.cy);

           if (hBitmap != IntPtr.Zero)
           {
               //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
               IntPtr hOld = (IntPtr)Win32Functions.SelectObject(hMemDC, hBitmap);

               //We copy the Bitmap to the memory device context.
               Win32Functions.BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, Win32_Constants.SRCCOPY);

               //We select the old bitmap back to the memory device context.
               Win32Functions.SelectObject(hMemDC, hOld);

               //We delete the memory device context.
               Win32Functions.DeleteDC(hMemDC);

               //We release the screen device context.
               Win32Functions.ReleaseDC(hWnd, hDC);

               //Image is created by Image bitmap handle and stored in local variable.
               image = (Image)System.Drawing.Image.FromHbitmap(hBitmap);

               // * For Debugging *
               //string strFileNameNPathWExtPNG = (Env.GetSnapshotDirectory() + @"\" + hWnd.ToString() + ".png");
               //image.Save(strFileNameNPathWExtPNG, System.Drawing.Imaging.ImageFormat.Png);

               //Release the memory for compatible bitmap.
               Win32Functions.DeleteObject(hBitmap);
               return image;
           }
           else
           {
               Log.Error(string.Format("{0}() - hBitmap is Null, something is wrong",
                         System.Reflection.MethodBase.GetCurrentMethod().Name));
               
               return null;
           }
       }

       #region log4net declaration

       private log4net.ILog Log
       {
           get
           {
               if (_logWrapper == null)
               {
                   _logWrapper = new LogServerClientWrapper(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
               }

               return _logWrapper.Log;
           }
       }

       private LogServerClientWrapper _logWrapper;

       #endregion
    }
}
